/*
 *  Copyright 2014 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

package com.cloudminds.resttest.service;

import com.cloudminds.resttest.api.RestApi;
import com.cloudminds.resttest.cache.ConfigCache;
import com.cloudminds.resttest.ice.AiasIceParams;
import com.cloudminds.resttest.vo.CallParams;
import com.cloudminds.resttest.webrtc.IceCandidate;
import com.cloudminds.resttest.webrtc.SessionDescription;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheConfig;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.NotYetConnectedException;
import java.util.LinkedList;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * WebSocket client implementation.
 *
 * <p>All public methods should be called from a looper executor thread
 * passed in a constructor, otherwise exception will be thrown.
 * All events are dispatched on the same thread.
 */

public class NewWebSocketChannelClient
{
	private static final String                             TAG            = "HS/WSChannelClient";
	private static final int                                CLOSE_TIMEOUT  = 1000;
	private final        WebSocketChannelEvents             events;
	private              WebSocketClient                    ws;
	private              String                             wsServerUrl;
	private              String                             regName;
	private              String                             regPwd;
	private              String                             token;
	private              WebSocketConnectionState           state;
	private final        Object                             closeEventLock = new Object();
	private              boolean                            closeEvent;
	private static       JSONArray                          answer;
	//private static       CopyOnWriteArraySet<WebSocketTest> webSocketSet   = new CopyOnWriteArraySet<WebSocketTest>();
    private Logger logger = LoggerFactory.getLogger(NewWebSocketChannelClient.class);

	
	// WebSocket send queue. Messages are added to the queue when WebSocket
	// client is not registered and are consumed in register() call.
	private final LinkedList<String>                         wsSendQueue;
	private       AppRTCClient.AssistantConnectionParameters connectionParameters;
	
	public String getToken()
	{
		return token;
	}
	
	/**
	 * Possible WebSocket connection states.
	 */
	public enum WebSocketConnectionState
	{
		NEW,
		CONNECTED,
		REGISTERED,
		CLOSED,
		ERROR
	}
	
	;
	
	/**
	 * Callback interface for messages delivered on WebSocket.
	 * All events are dispatched from a looper executor thread.
	 */
	public interface WebSocketChannelEvents
	{
		public JSONArray onWebSocketMessage(final String message);
		
		public void onWebSocketClose();
		
		public void onWebSocketConnectError(final String description);
		
		public void onWebSocketRegisterError(final String description);
		
		public void onWebSocketRegisterred(final JSONObject data);
	}
	
	public void setConnectionParameters(AppRTCClient.AssistantConnectionParameters connectionParameters)
	{
		this.connectionParameters = connectionParameters;
	}
	
	public NewWebSocketChannelClient(WebSocketChannelEvents events)
	{
		this.events = events;
		wsSendQueue = new LinkedList<String>();
		state = WebSocketConnectionState.NEW;
	}
	
	public WebSocketConnectionState getState()
	{
		return state;
	}
	
	public static JSONArray getAnswer()
	{
		return answer;
	}
	
	public void connect(final String wsUrl, final String name, final String password, final String sessionId)
	{
		checkIfCalledOnValidThread();
		System.out.println("On connect entry!\n");
		if (state != WebSocketConnectionState.NEW && state != WebSocketConnectionState.CLOSED)
		{
			System.out.print("WebSocket is already connected.");
		}
		wsServerUrl = wsUrl;
		regName = name;
		regPwd = password;
		closeEvent = false;
		
		System.out.println("Connecting WebSocket to: " + wsUrl + ".   regName = " + regName + "\n");
		URI serverUri = null;
		try
		{
			serverUri = new URI(wsUrl);
		}
		catch (URISyntaxException e)
		{
			e.printStackTrace();
		}
		
		ws = new WebSocketClient(serverUri, new Draft_6455())
		{
			@Override
			public void onOpen(ServerHandshake handshakedata)
			{
				System.out.println("WebSocket connection opened to: " + wsServerUrl + "\n");
				
				state = WebSocketConnectionState.CONNECTED;
				register(regName, regPwd, sessionId);
			}
			
			@Override
			public void onMessage(final String message)
			{
				
				if (state != WebSocketConnectionState.REGISTERED)
				{
					
					try
					{
						JSONObject json = new JSONObject(message);
						String msgId = json.getString("id");
						
						if (msgId.equals("loginResponse"))
						{
							System.out.println("WSS->C: " + message + "\n");
							
							String response = json.getString("response");
							System.out.println("run: loginResponse " + response);
							if (response.equals("rejected"))
							{
								String detail = json.getString("message");
								final String errorMessage = "Failed to register: " + detail;
								System.out.println(errorMessage);
								if (state != WebSocketConnectionState.ERROR)
								{
									state = WebSocketConnectionState.ERROR;
								}
								events.onWebSocketRegisterError(errorMessage);
							}
							else
							{
								System.out.println("Register to assistant websocket server completed.");
								state = WebSocketConnectionState.REGISTERED;
								token = json.getString("token");
								//webSocketClosing=false;
								// Send any previously accumulated messages.
								System.out.println("run: 连接成功");
								for (String sendMessage : wsSendQueue)
								{
									send(sendMessage);
								}
								wsSendQueue.clear();
								
								String sessionId = "";
								boolean isNeedMediaReconnect = false;
								JSONObject data = null;
								if (json.has("data"))
								{
									data = json.getJSONObject("data");
									sessionId = data.getString("sid");
									if (data.has("mediaReconnect"))
									{
										isNeedMediaReconnect = true;
									}
									
									AiasIceParams.setEndpoints(data.getString("aiasEndpoints"));
									AiasIceParams.setGridLocator(data.getString("aiasGridLocator"));
									ConfigCache.instance().setAyncMessage("login", sessionId);
								}
								//  events.onWebSocketRegisterred(data);
								
								if (isNeedMediaReconnect)
								{
									answer = events.onWebSocketMessage("{\"id\":\"info\",\"type\":\"mediaReconnect\"}");
								}
							}
						}
					}
					catch (JSONException e)
					{
						System.out.println("WebSocket message JSON received from HariServer parsing error: " + e.toString());
					}
				}
				
				if (state == WebSocketConnectionState.CONNECTED || state == WebSocketConnectionState.REGISTERED)
				{
					 events.onWebSocketMessage(message);
				}
			}
			
			@Override
			public void onClose(int code, String reason, boolean remote)
			{
				System.out.println("WebSocket connection closed. Code: " + code + ". Reason: " + reason + ". State: " + state + " rcuId: " + connectionParameters.username);
				//断网CODE=5
				synchronized (closeEventLock)
				{
					closeEvent = true;
					closeEventLock.notify();
				}
				
				if (state != WebSocketConnectionState.CLOSED)
				{
					state = WebSocketConnectionState.CLOSED;
					events.onWebSocketClose();
				}
			}
			
			@Override
			public void onError(Exception ex)
			{
				ex.printStackTrace();
			}
		};
		
		ws.setConnectionLostTimeout(600);
		try
		{
			System.out.println("WebSocket connect...");
			ws.connect();
		}
		catch (Exception e)
		{
			final String errorMessage = "WebSocket connection error: " + e.getMessage();
			System.out.println(errorMessage);
			
			if (state != WebSocketConnectionState.ERROR)
			{
				state = WebSocketConnectionState.ERROR;
				events.onWebSocketConnectError(errorMessage);
			}
		}
        /*Map<Object, Object> map1 = new HashMap<Object, Object>();
        map1.put("sid",sid);
        map1.put("data",rdata);*/
	}
	
	public void rebind(final String sessionId)
	{
		System.out.println("Rebind WebSocket ... with username: " + regName);
		
		state = WebSocketConnectionState.CONNECTED;
		register(regName, regPwd, sessionId);
	}
	
	public void register(final String regName, final String pwd, final String sessionId)
	{
		checkIfCalledOnValidThread();
		this.regName = regName;
		if (state != WebSocketConnectionState.CONNECTED)
		{
			System.out.println("WebSocket register() in state " + state);
			return;
		}
		System.out.println("Registering name " + regName + ".");
		JSONObject json = new JSONObject();
		JSONObject data = new JSONObject();
		try
		{
			json.put("id", "login");
			json.put("name", regName);//regName+"#"+PreferenceUtils.getPrefString(BaseConstants.PRE_KEY_TENANTID,"10086")
			if (!sessionId.isEmpty()) { json.put("sid", sessionId); }
			json.put("role", "client");
			json.put("password", pwd);
			json.put("appType", "pepper");
			
			data.put("tenantId", CallParams.TENANT_ID);
			data.put("rcuId", connectionParameters.rcuID);
			data.put("lang", "CH");
			data.put("robotId", connectionParameters.rcuID);
			data.put("rcuFirstConnection", false);
			data.put("rcuFirstConnectionDate", String.valueOf(System.currentTimeMillis()));
			json.put("data", data);
		}
		catch (JSONException e)
		{
			System.out.println("WebSocket register JSON error: " + e.getMessage());
			return;
		}
		
		System.out.println("C->WSS: " + json.toString());
		ws.send(json.toString());
		
	}
	
	public void send(String message)
	{
		System.out.print("===============" + state);
		checkIfCalledOnValidThread();
		switch (state)
		{
			case NEW:
			case CONNECTED:
				// Store outgoing messages and send them after websocket client
				// is registered.
				//LogUtils.d(TAG, "WS ACC: " + message);
				wsSendQueue.add(message);
                logger.info("wsSendQueue   add...");
				return;
			case ERROR:
			case CLOSED:
				//LogUtils.e(TAG, "WebSocket send() in error or closed state : " + message);
				return;
			case REGISTERED:
				//LogUtils.d(TAG, "C->WSS: " + message);
				ws.send(message);
                logger.info("send.......");
				break;
		}
		return;
	}
	
	public void disconnect(boolean waitForComplete)
	{
		checkIfCalledOnValidThread();
		System.out.println("Disonnect WebSocket. State: " + state);
		
		// Close WebSocket in CONNECTED or ERROR states only.
		if (state == WebSocketConnectionState.CONNECTED || state == WebSocketConnectionState.REGISTERED || state == WebSocketConnectionState.ERROR)
		{
			
			if (ws != null) { ws.close(); }
			state = WebSocketConnectionState.CLOSED;
			
			// Wait for websocket close event to prevent websocket library from
			// sending any pending messages to deleted looper thread.
			if (waitForComplete)
			{
				synchronized (closeEventLock)
				{
					while (!closeEvent)
					{
						try
						{
							closeEventLock.wait(CLOSE_TIMEOUT);
							break;
						}
						catch (InterruptedException e)
						{
							System.out.println("Wait error: " + e.toString());
						}
					}
				}
			}
		}
		System.out.println("Disonnecting WebSocket done.");
	}
	
	//  private void reportError(final String errorMessage) {
	//    Log.e(TAG, errorMessage);
	//    executor.execute(new Runnable() {
	//      @Override
	//      public void run() {
	//        if (state != WebSocketConnectionState.ERROR) {
	//          state = WebSocketConnectionState.ERROR;
	//          events.onWebSocketError(errorMessage);
	//        }
	//      }
	//    });
	//  }
	
	// Helper method for debugging purposes. Ensures that WebSocket method is
	// called on a looper thread.
	private void checkIfCalledOnValidThread()
	{

	}

	public void logout(final String sessionId)
	{
		JSONObject json = new JSONObject();
		try
		{
			json.put("id", "logout");
			json.put("name", connectionParameters.username);
			json.put("sid", sessionId);
			json.put("role", "client");
			json.put("password", connectionParameters.password);

		}
		catch (JSONException e)
		{
			System.out.println("WebSocket register JSON error: " + e.getMessage());
			return;
		}

		System.out.println("run: sendLogout " + json.toString());
		ws.send(json.toString());
       // state = WebSocketConnectionState.CLOSED;
	}


	public void sendCallUpdate(final String sessionId, final IceCandidate candidate) {

		if (null == ws) {
			System.out.println("wsClient is null, return");
			return;
		}

		JSONObject json = new JSONObject();

		jsonPut(json, "id", "callUpdate");
		jsonPut(json, "sid", sessionId);
		jsonPut(json, "from", connectionParameters.username);
		jsonPut(json, "role","client");
		if(candidate!=null) {
			JSONObject candidateJson = new JSONObject();

			jsonPut(candidateJson, "candidate", candidate.sdp);
			jsonPut(candidateJson, "sdpMid", candidate.sdpMid);
			jsonPut(candidateJson, "sdpMLineIndex", candidate.sdpMLineIndex);

			jsonPut(json, "candidate", candidateJson);
		}
		System.out.println("run: sendCallUpdate");
		ws.send(json.toString());
	}


	private static void jsonPut(JSONObject json, String key, Object value) {
		try {
			json.put(key, value);
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
	}

	public String sendCallStart(final String sessionId, final SessionDescription sdp) {
		try {
			if (null == ws) {
				System.out.println("Send callstart,bt wsClient is null");
				return "wsClient is null";
			}
			JSONObject json = new JSONObject();
			jsonPut(json, "id", "callStart");
			if(!sessionId.isEmpty()){
				jsonPut(json, "sid", sessionId);
			}
			jsonPut(json, "from", connectionParameters.username);
			jsonPut(json, "helper", connectionParameters.customer);
			jsonPut(json, "sdpOffer", sdp.description);
			jsonPut(json, "token", getToken());
			jsonPut(json, "rcuId", "");
			jsonPut(json, "urgent", "hi@cloudminds.com");
			jsonPut(json, "appType", connectionParameters.robotType);
			jsonPut(json, "role", "client");
			jsonPut(json,"destroyMedia",connectionParameters.destroyMedia);
			System.out.println("run: sendcallStart \n "+json.toString());

			send(json.toString());
			return "call Success....";
		} catch (NotYetConnectedException e) {
			 e.printStackTrace();
			return e.toString();
		}
	}

}
